﻿using UnityEngine;
using System.Collections.Generic;

namespace CWM.Skinn
{
    public static partial class SkinnEx
    {
        /// <summary>
        /// If the shape name is blank the source's name will be used. If there is a existing shape with the same name it will be replaced with the new shape. Returns false if the vertex counts are mismatched.
        /// </summary>
        /// <param name="target"></param>
        /// <param name="source"></param>
        /// <param name="shapeName"></param>
        /// <param name="normals"></param>
        /// <param name="tangents"></param>
        /// <returns></returns>
        public static bool AddBlendshape(this Mesh target, Mesh source,
            string shapeName = "", bool normals = true, bool tangents = true, bool canoOverwrite = true)
        {
            if (!source || !target) return false;
                     
            var sourceVertices = source.vertices;
            var sourceNormals = source.normals;
            var sourceTangents = source.tangents;

            var targetVertices = target.vertices;
            var targetNormals = target.normals;
            var targetTangents = target.tangents;

            if (!target.CanApplyVertices(source))
            {
                return false;
            }

            var vertexCount = targetVertices.Length;
            var deltasVertices = new Vector3[vertexCount];
            var deltasNormals = new Vector3[vertexCount];
            var deltaTangents = new Vector3[vertexCount];


            var hasNormals = !IsNullOrEmpty(targetNormals, vertexCount) && !IsNullOrEmpty(sourceNormals, vertexCount);
            var hasTangents = !IsNullOrEmpty(targetTangents, vertexCount) && !IsNullOrEmpty(sourceTangents, vertexCount);

            for (int i = 0; i < deltasVertices.Length; i++)
            {
                var delta = targetVertices[i] + (targetVertices[i] - sourceVertices[i]);
                deltasVertices[i] = targetVertices[i] - delta;

                if(hasNormals)
                {
                    var deltaNormal = targetNormals[i] + (targetNormals[i] - sourceNormals[i]);
                    deltasNormals[i] = targetNormals[i] - deltaNormal;
                }

                if (hasTangents)
                {
                    var deltaNormal = (Vector3)targetTangents[i] + ((Vector3)targetTangents[i] - (Vector3)sourceTangents[i]);
                    deltaTangents[i] = (Vector3)targetTangents[i] - deltaNormal;
                }
            }

            var name = string.IsNullOrEmpty(shapeName) ? source.name : shapeName;
            var shapeIndex = target.GetBlendShapeIndex(name);
            if (shapeIndex > 0 && canoOverwrite)
            {
                var blendshapes = target.GetBlendshapes();
                target.ClearBlendShapes();

                for (int i = 0; i < blendshapes.Count; i++)
                {
                    var shape = blendshapes[i];

                    if (i == shapeIndex)
                    {

                        if (SkinnInternalAsset.DevLog)
                        {
                            var log = string.Format("Blend-shape :{0} Overwritten", name);
                            log += System.Environment.NewLine;
                            log += string.Format("vertexCount: {0}", vertexCount);
                            log += System.Environment.NewLine;
                            log += string.Format("deltasVertices: {0}", deltasVertices.Length);
                            log += System.Environment.NewLine;
                            log += string.Format("deltasNormals: {0}", hasNormals);
                            log += System.Environment.NewLine;
                            log += string.Format("deltaTangents: {0}", hasTangents);

                            Debug.Log(log);
                        }

                        target.AddBlendShapeFrame(
                            name,
                            100f,
                            deltasVertices,
                            normals && hasNormals ? deltasNormals : null,
                            tangents && hasTangents ? deltaTangents : null);

                        continue;
                    }

                    var frameCount = blendshapes[i].frames.Count;
                    for (int ii = 0; ii < frameCount; ii++)
                    {
                        var frame = blendshapes[i].frames[ii];
                        target.AddBlendShapeFrame(
                            shape.name,
                            frame.frameWeight,
                             !IsNullOrEmpty(frame.shapeDeltas, deltasVertices.Length) ? frame.shapeDeltas : new Vector3[deltasVertices.Length],
                             !IsNullOrEmpty(frame.shapeNormals, deltasVertices.Length) ? frame.shapeNormals : null,
                             !IsNullOrEmpty(frame.shapeTangents, deltasVertices.Length) ? frame.shapeTangents : null);
                    }
                }
            }
            else if(shapeIndex == -1)
            {
                if (SkinnInternalAsset.DevLog)
                {
                    var log = string.Format("Blend-shape :{0} Added", name);
                    log += System.Environment.NewLine;
                    log += string.Format("vertexCount: {0}", vertexCount);
                    log += System.Environment.NewLine;
                    log += string.Format("deltasVertices: {0}", deltasVertices.Length);
                    log += System.Environment.NewLine;
                    log += string.Format("deltasNormals: {0}", hasNormals);
                    log += System.Environment.NewLine;
                    log += string.Format("deltaTangents: {0}", hasTangents);
                    Debug.Log(log);
                }

                target.AddBlendShapeFrame(
                           name,
                           100f,
                           deltasVertices,
                           normals && hasNormals ? deltasNormals : null,
                           tangents && hasTangents ? deltaTangents : null);
            }
            return true;

        }

        public static void RemoveBlendshape(this Mesh mesh, int index)
        {
            if (index < 0 || !mesh || mesh.blendShapeCount < 1) return;

            var blendshapes = mesh.GetBlendshapes();
            mesh.ClearBlendShapes();

            for (int i = 0; i < blendshapes.Count; i++)
            {
                if (i == index) continue;

                var shape = blendshapes[i];

                for (int ii = 0; ii < shape.frames.Count; ii++)
                {
                    var deltaVertices = shape.frames[ii].shapeDeltas;
                    var deltaNormals = shape.frames[ii].shapeNormals;
                    var deltaTangents = shape.frames[ii].shapeTangents;

                    mesh.AddBlendShapeFrame(shape.name, shape.frames[ii].frameWeight,
                    IsNullOrEmpty(deltaVertices) ? new Vector3[mesh.vertices.Length] : deltaVertices,
                    IsNullOrEmpty(deltaNormals) ? null : deltaNormals,
                    IsNullOrEmpty(deltaTangents) ? null : deltaTangents);
                }
            }
        }

        public static void RemoveBlendshape(this Mesh mesh, string name)
        {
            if (!mesh || mesh.blendShapeCount < 1) return;
            RemoveBlendshape(mesh, mesh.GetBlendshapeNames().GetIndex(name));
        }

        public static void RemoveBlendshapeData(this Mesh mesh, int index, bool removeVertices, bool removeNormals, bool removeTangents)
        {
            if (index < 0 || !mesh || mesh.blendShapeCount < 1) return;

            var blendshapes = mesh.GetBlendshapes();
            mesh.ClearBlendShapes();

            for (int i = 0; i < blendshapes.Count; i++)
            {
                var shape = blendshapes[i];

                for (int ii = 0; ii < shape.frames.Count; ii++)
                {
                    var deltaVertices = shape.frames[ii].shapeDeltas;
                    var deltaNormals = shape.frames[ii].shapeNormals;
                    var deltaTangents = shape.frames[ii].shapeTangents;

                    if (i == index)
                    {
                        mesh.AddBlendShapeFrame(shape.name, shape.frames[ii].frameWeight,
                                  IsNullOrEmpty(deltaVertices)  || removeVertices ? new Vector3[mesh.vertices.Length] : deltaVertices,
                                  IsNullOrEmpty(deltaNormals) || removeNormals ? null : deltaNormals,
                                  IsNullOrEmpty(deltaTangents) || removeTangents ? null : deltaTangents);
                    }
                    else
                    {
                        mesh.AddBlendShapeFrame(shape.name, shape.frames[ii].frameWeight,
                                    IsNullOrEmpty(deltaVertices) ? new Vector3[mesh.vertices.Length] : deltaVertices,
                                    IsNullOrEmpty(deltaNormals) ? null : deltaNormals,
                                    IsNullOrEmpty(deltaTangents) ? null : deltaTangents);
                    }
                }
            }
        }

        public static void RemoveBlendshapeData(this Mesh mesh, string name, bool removeVertices, bool removeNormals, bool removeTangents)
        {
            if (!mesh || mesh.blendShapeCount < 1) return;
            RemoveBlendshapeData(mesh, mesh.GetBlendshapeNames().GetIndex(name), removeVertices, removeNormals, removeTangents);
        }

        public static void RemoveBlendshapeData(this Mesh mesh, bool removeVertices, bool removeNormals, bool removeTangents)
        {
            if (!mesh || mesh.blendShapeCount < 1) return;

            var blendshapes = mesh.GetBlendshapes();
            mesh.ClearBlendShapes();

            for (int i = 0; i < blendshapes.Count; i++)
            {
                var shape = blendshapes[i];

                for (int ii = 0; ii < shape.frames.Count; ii++)
                {
                    var deltaVertices = shape.frames[ii].shapeDeltas;
                    var deltaNormals = shape.frames[ii].shapeNormals;
                    var deltaTangents = shape.frames[ii].shapeTangents;

                    mesh.AddBlendShapeFrame(shape.name, shape.frames[ii].frameWeight,
                                 IsNullOrEmpty(deltaVertices) || removeVertices ? new Vector3[mesh.vertices.Length] : deltaVertices,
                                 IsNullOrEmpty(deltaNormals) || removeNormals ? null : deltaNormals,
                                 IsNullOrEmpty(deltaTangents) || removeTangents ? null : deltaTangents);
                }
            }
        }

        public static void CombineDuplicateShapes(this Mesh mesh)
        {
            var blendshapes = mesh.GetBlendshapes();
            mesh.ClearBlendShapes();
            var finishedShapes = new List<int>();
            var vertexCount = mesh.vertices.Length;

            for (int i = 0; i < blendshapes.Count; i++)
            {
                if (finishedShapes.Contains(i)) continue;

                var shape = new Blendshape
                {
                    name = blendshapes[i].name,
                    frames = blendshapes[i].frames
                };


                for (int ii = 0; ii < blendshapes.Count; ii++)
                {
                    if (i == ii) continue;
                    if (finishedShapes.Contains(ii)) continue;

                    var otherShape = blendshapes[ii];
                    if (shape.name != otherShape.name) continue;
                    if (shape.frames.Count != otherShape.frames.Count)
                    {
                        Debug.LogWarningFormat("Shape: {0} frames do not match skipping.", shape.name);
                        continue;
                    }

                    finishedShapes.Add(ii);

                    //Debug.Log(shape.name);

                    for (int iii = 0; iii < shape.frames.Count; iii++)
                    {
                        var deltaVertices = shape.frames[iii].shapeDeltas;
                        var deltaVerticesOther = otherShape.frames[iii].shapeDeltas;

                        var deltaNormals = shape.frames[iii].shapeNormals;
                        var deltaNormalsOther = otherShape.frames[iii].shapeNormals;

                        var deltaTangents = shape.frames[iii].shapeTangents;
                        var deltaTangentsOther = otherShape.frames[iii].shapeTangents;

                        var vertex = !IsNullOrEmpty(deltaVertices) && !IsNullOrEmpty(deltaVerticesOther);
                        var normal = !IsNullOrEmpty(deltaNormals) && !IsNullOrEmpty(deltaNormalsOther);
                        var tanget = !IsNullOrEmpty(deltaTangents) && !IsNullOrEmpty(deltaTangentsOther);

                        for (int iv = 0; iv < vertexCount; iv++)
                        {
                            if (vertex) deltaVertices[iv] += deltaVerticesOther[iv];
                            if (normal) deltaNormals[iv] += deltaNormalsOther[iv];
                            if (tanget) deltaTangents[iv] += deltaTangentsOther[iv];
                        }

                        shape.frames[iii].shapeDeltas = deltaVertices;
                        shape.frames[iii].shapeNormals = deltaNormals;
                        shape.frames[iii].shapeTangents = deltaTangents;
                    }
                }

                for (int ii = 0; ii < shape.frames.Count; ii++)
                {
                    var deltaVertices = shape.frames[ii].shapeDeltas;
                    var deltaNormals = shape.frames[ii].shapeNormals;
                    var deltaTangents = shape.frames[ii].shapeTangents;

                    mesh.AddBlendShapeFrame(
                  shape.name, shape.frames[ii].frameWeight,
                  IsNullOrEmpty(deltaVertices) ? new Vector3[vertexCount] : deltaVertices,
                 IsNullOrEmpty(deltaNormals) ? null : deltaNormals,
                 IsNullOrEmpty(deltaTangents) ? null : deltaTangents);
                }

            }
        }

    }
}